#include "GLWidget.h"
#include "MainWindow.h"
#include "../Core/Exceptions.h"
#include "../Core/Materials.h"

#include "../Core/Constants.h"
#include "../Core/HCoordinates3.h"

#include <QFileDialog>
#include <fstream>
using namespace std;

namespace cagd
{
    // default/special constructor
    GLWidget::GLWidget(QWidget *parent, const QGLFormat &format): QGLWidget(format, parent)
    {
        timer = new QTimer(this);
        timer->setInterval(0);

        connect(timer, SIGNAL(timeout()), this, SLOT(animate()));

        _parent = parent;
    }

    // destructor
    GLWidget::~GLWidget()
    {
        if (_image_of_data_points)
        {
            delete _image_of_data_points, _image_of_data_points = 0;
        }

        // free every dynamically allocated object
        if (_cc)
        {
            delete _cc, _cc = 0;
        }

        if (_image_cc)
        {
            delete _image_cc, _image_cc = 0;
        }

        if (_icc)
        {
            delete _icc, _icc = 0;
        }

        if (_image_icc)
        {
            delete _image_icc, _image_icc = 0;
        }

        if (_tbs)
        {
            delete _tbs, _tbs = 0;
        }

        if (_image_tbs)
        {
            delete _image_tbs, _image_tbs = 0;
        }

        if (_itbs)
        {
            delete _itbs, _itbs = 0;
        }

        if (_image_itbs)
        {
            delete _image_itbs, _image_itbs = 0;
        }

        if (_trigo_population)
        {
            delete _trigo_population, _trigo_population = 0;
        }
        if (timer) {
            delete timer, timer = 0;
        }
    }

    // initializer method
    // it is called once before the first call to paintGL() or resizeGL(),
    // and then once whenever the widget has been assigned a new QGLContext
    void GLWidget::initializeGL()
    {
        glewInit();

        // define a projection matrix
        glMatrixMode(GL_PROJECTION);

        _aspect = (GLfloat)width() / (GLfloat)height();
        _fovy = 45.0;
        _z_near = 1.0;
        _z_far = 1000.0;

        glLoadIdentity();
        gluPerspective(_fovy, _aspect, _z_near, _z_far);

        // define a model view matrix
        glMatrixMode(GL_MODELVIEW);

        _eye[2] = 6.0;
        _up[1] = 1.0;

        glLoadIdentity();
        gluLookAt(_eye[0], _eye[1], _eye[2], _center[0], _center[1], _center[2], _up[0], _up[1], _up[2]);

        // enable the required OpenGL states

        // enable depth testing
        glEnable(GL_DEPTH_TEST);

        // ensure unit normal vectors
        glEnable(GL_NORMALIZE);

        // enable default lighting
        glEnable(GL_LIGHTING);

        // enable default light source 0, which is a directional light with grey ambient, diffuse and specular intensities
        glEnable(GL_LIGHT0);

        // set the background color
        //        glClearColor(0.0, 0.0, 0.0, 1.0);
        glClearColor(1.0, 1.0, 1.0, 1.0);

        //  create/load your geometry

        if (!_mouse.LoadFromOFF("Models/mouse.off", GL_TRUE))
        {
            throw Exception("Could not load the model file!");
        }
        else
        {
            if (!_mouse.UpdateVertexBufferObjects())
            {
                throw Exception("Could not create the vertex buffer object of the model!");
            }
        }

        _trans_x = _trans_y = _trans_z = 0.0;
        _scale = 0.7;
        _angle_x = _angle_y = _angle_z = 0.0;

        _cc = 0;
        _image_cc = 0;
        _icc = 0;
        _image_icc = 0;

        _alpha = PI / 2.0;
        ((MainWindow*)_parent)->_settings_widget->alpha_spin_box->setValue(2.0);
        _beta = PI / 3.0;
        ((MainWindow*)_parent)->_settings_widget->beta_spin_box->setValue(3.0);

        // orders
        _n = 3;
        ((MainWindow*)_parent)->_settings_widget->data_point_size_u_spin_box->setValue(_n);
        _m = 2;
        ((MainWindow*)_parent)->_settings_widget->data_point_size_v_spin_box->setValue(_m);

        _individual_number = 6;
        ((MainWindow*)_parent)->_settings_widget->individual_number_spin_box->setValue(_individual_number);

        _individual_number_to_show = 1;
        ((MainWindow*)_parent)->_settings_widget->individual_number_to_show_spin_box->setValue(_individual_number_to_show);

        _data_point_element_u_index = 0;
        _data_point_element_v_index = 0;

        _image_of_data_points = 0;

        initialDataPoints();
    }

    void GLWidget::initialDataPoints() {

        if (_image_of_data_points)
        {
            delete _image_of_data_points, _image_of_data_points = 0;

            _image_of_data_points = new TrigonometricBernsteinSurface3(_alpha, _n, _beta, _m);
        }
        else
        {
            _image_of_data_points = new TrigonometricBernsteinSurface3(_alpha, _n, _beta, _m);
        }

        GLdouble du = 6.0 / (2 * _n);
        GLdouble dv = 6.0 / (2 * _m);

        _data_net_to_interpolate.ResizeRows(2 * _n + 1);
        _data_net_to_interpolate.ResizeColumns(2 * _m + 1);

        ((MainWindow*)_parent)->_settings_widget->data_point_element_u_combo_box->clear();
        ((MainWindow*)_parent)->_settings_widget->data_point_element_v_combo_box->clear();

        for (GLuint i = 0; i < 2 * _n + 1; ++i)
        {
            ((MainWindow*)_parent)->_settings_widget->data_point_element_u_combo_box->addItem(QString::number(i),i);
        }

        for (GLuint j = 0; j < 2 * _m + 1; ++j)
        {
            ((MainWindow*)_parent)->_settings_widget->data_point_element_v_combo_box->addItem(QString::number(j),j);
        }

        for (GLuint i = 0; i < 2 * _n + 1; ++i)
        {
            for (GLuint j = 0; j < 2 * _m + 1; ++j)
            {
                //_data_net_to_interpolate(i, j) = (*_tbs)(i, j);

                DCoordinate3 &ref = _data_net_to_interpolate(i, j);

                ref[0] = -3.0 + i * du;
                ref[1] = -3.0 + j * dv;
                ref[2] = -1 + 2 * (GLdouble)rand() / (GLdouble)RAND_MAX;

                if (_image_of_data_points)
                {
                    (*_image_of_data_points)(i, j) = _data_net_to_interpolate(i,j);
                }
            }
        }
        //            cout << _data_net_to_interpolate << endl;

        //            _itbs = 0;
        //            _itbs = new TrigonometricBernsteinSurface3(alpha, _n, beta, _m);

        //            if (!_itbs)
        //            {
        //                throw Exception("Could not create interpolating trigonometric Bernstein like surface!");
        //            }
        //            else
        //            {
        //                if (!_itbs->UpdateDataForInterpolation(_u_knot_vector, _v_knot_vector, _data_net_to_interpolate))
        //                {
        //                    throw Exception("Could not solve the surface interpolation problem!");
        //                }
        //                else
        //                {
        //                    cout << "The area of the interpolating surface is " << (*_itbs).Area(100, 100) << endl;
        //                    if (!_itbs->UpdateVertexBufferObjectsOfData())
        //                    {
        //                        throw Exception("Could not create the vertex buffer object of the interpolating surface's control net!");
        //                    }

        //                    _image_itbs = _itbs->GenerateImage(u_div_point_count, v_div_point_count);

        //                    if (!_image_itbs)
        //                    {
        //                        throw Exception("Could not generate the image of the interpolating surface!");
        //                    }
        //                    else
        //                    {
        //                        if (!_image_itbs->UpdateVertexBufferObjects())
        //                        {
        //                            throw Exception("Could not create the vertex buffer objects of the interpolating surface's image!");
        //                        }
        //                    }
        //                }
        //            }

        if (_image_of_data_points)
        {
            _image_of_data_points->UpdateVertexBufferObjectsOfData();
        }

        initialPopulation();
    }

    void GLWidget::initialPopulation() {
        _tbs = 0;
        _itbs = 0;
        _image_tbs = 0;
        _image_itbs = 0;

        best = 0;
        _trigo_population = 0;
        _trigo_population = new TrigoBernsteinSurfPopulation(_individual_number, _alpha, _n, _beta, _m, _data_net_to_interpolate, 1, 50, 50);
        best = _trigo_population->getBest();
        _show = GL_TRUE;
        //        _trigo_population->evolve();
    }

    void GLWidget::evolve(int checked) {
        min_length = -1;
        if (checked > 0) {
            //            timer->setInterval(5000);
            timer->start();
        } else {
            timer->stop();
        }
    }

    void GLWidget::animate() {
        //        if (min_length = -1) {
        //            RowMatrix<TrigoBernsteinSurfPopulation::Individual*> inds = _trigo_population->getAllIndividual();
        //            min_length = 0;
        //            for (GLuint i = 0; i < inds.GetColumnCount(); i++) {
        //                min_length += inds[i]->getFitness();
        //            }
        //        }
        _trigo_population->evolve();
        //        GLdouble act_length = 0;
        //        RowMatrix<TrigoBernsteinSurfPopulation::Individual*> inds = _trigo_population->getAllIndividual();
        //        for (GLuint i = 0; i < inds.GetColumnCount(); i++) {
        //            act_length += inds[i]->getFitness();
        //        }
        //        if (act_length < min_length) {
        //            min_length = act_length;
        //        }
        //        if (act_length / (GLdouble)inds.GetColumnCount() < _trigo_population->getBest()->getFitness() + 0.01) {
        //            timer->stop();
        //            for (GLuint i = 0; i < inds.GetColumnCount(); i++) {
        //                cout << inds[i]->getFitness() << /*" knots-> " << inds[i]->getKnotsVector() <<*/ endl;
        //            }
        //            cout << "stopped!" << endl;
        //        }

        if (best && best->getImage())
            updateGL();
    }

    // rendering method
    void GLWidget::paintGL()
    {
        // clear the color and depth buffers
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // save (or duplicate) the modelview matrix
        glPushMatrix();

        // perform your transformations and render your geometry
        glRotated(_angle_x, 1.0, 0.0, 0.0);
        glRotated(_angle_y, 0.0, 1.0, 0.0);
        glRotated(_angle_z, 0.0, 0.0, 1.0);

        glTranslated(_trans_x, _trans_y, _trans_z);

        glScaled(_scale, _scale, _scale);

        if (best && _show && _trigo_population)
        {

            if (_image_of_data_points)
            {
                glDisable(GL_LIGHTING);

                glColor3f(0.0, 1.0, 1.0);
                _image_of_data_points->RenderData();


                glColor3f(1.0, 0.0, 0.0);
                glPointSize(10.0);
                _image_of_data_points->RenderData(GL_POINTS);
                glPointSize(1.0);
                glEnable(GL_LIGHTING);

                MatFBGold.Apply();
                _image_itbs = _image_of_data_points->GenerateImage(100, 100);
                _image_itbs->UpdateVertexBufferObjects();
                _image_itbs->Render();
            }

//            glColor3f(0, 1, 0);
//            glPointSize(45.0);
//            //                _tbs->RenderData(GL_POINTS);
//            glPointSize(1.0);
//            glEnable(GL_LIGHTING);
//            MatFBGold.Apply();
//            //                _image_tbs->Render();

            best = _trigo_population->getBest();
            cout << "Best Length: " << best->getFitness() << endl;
            MatFBPearl.Apply();
            if (best->getImage()) {
                best->getImage()->Render();
                //                    cout << "RENDERED" << endl;
            }
            else {
                cout << "NO IMAGE" << endl;
                best->render();
                best->getImage()->Render();
            }
        }


        // restore the original modelview matrix
        glPopMatrix();
    }

    // resizing method
    void GLWidget::resizeGL(int w, int h)
    {
        // set the viewport
        glViewport(0, 0, w, h);

        // define the projection matrix
        glMatrixMode(GL_PROJECTION);

        _aspect = (GLfloat)w / (GLfloat)h;

        glLoadIdentity();
        gluPerspective(_fovy, _aspect, _z_near, _z_far);

        // revert to the modelview matrix
        glMatrixMode(GL_MODELVIEW);

        // update your scene, i.e., invoke indirectly the method paintGL()
        updateGL();

    }

    // implementation of public event handlers
    void GLWidget::set_angle_x(int value)
    {
        if (_angle_x != (double)value)
        {
            _angle_x = value;
            updateGL();
        }
    }

    void GLWidget::set_angle_y(int value)
    {
        if (_angle_y != (double)value)
        {
            _angle_y = value;
            updateGL();
        }
    }

    void GLWidget::set_angle_z(int value)
    {
        if (_angle_z != (double)value)
        {
            _angle_z = value;
            updateGL();
        }
    }

    void GLWidget::set_trans_x(double value)
    {
        if (_trans_x != value)
        {
            _trans_x = value;
            updateGL();
        }
    }

    void GLWidget::set_trans_y(double value)
    {
        if (_trans_y != value)
        {
            _trans_y = value;
            updateGL();
        }
    }

    void GLWidget::set_trans_z(double value)
    {
        if (_trans_z != value)
        {
            _trans_z = value;
            updateGL();
        }
    }

    void GLWidget::set_zoom_factor(double value)
    {
        if (_scale != value)
        {
            _scale = value;
            updateGL();
        }
    }

    void GLWidget::reset_to_original_view()
    {
        _trans_x = _trans_y = _trans_z = 0.0;
        _scale = 1.0;
        _angle_x = _angle_y = _angle_z = 0.0;

        updateGL();
    }

    void GLWidget::set_alpha(double value) {
        if (_alpha != (PI / value))
        {
            _alpha = PI / value;

            initialDataPoints();

            updateGL();
        }
    }

    void GLWidget::set_beta(double value) {
        if (_beta != (PI / value))
        {
            _beta = PI / value;

            initialDataPoints();

            updateGL();
        }
    }

    void GLWidget::set_data_point_size_u(int value) {
        if (_n != value)
        {
            _n = value;

            initialDataPoints();

            updateGL();
        }
    }

    void GLWidget::set_data_point_size_v(int value) {
        if (_m != value)
        {
            _m = value;

            initialDataPoints();

            updateGL();
        }
    }

    void GLWidget::set_individual_number(int value) {
        if ((_individual_number != value) && (_individual_number_to_show <= value))
        {
            _individual_number = value;

            initialPopulation();

            updateGL();
        }
    }

    void GLWidget::set_individual_number_to_show(int value) {
        if ((_individual_number_to_show != value) && (_individual_number >= value))
        {
            _individual_number_to_show = value;

            updateGL();
        }
    }

    void GLWidget::set_data_point_elements_u(int value) {
        if (((MainWindow*)_parent)->_settings_widget->data_point_element_u_combo_box->hasFocus())
        {
            _data_point_element_u_index = value;
        }
    }

    void GLWidget::set_data_point_elements_v(int value) {
        if (((MainWindow*)_parent)->_settings_widget->data_point_element_v_combo_box->hasFocus())
        {
            _data_point_element_v_index = value;

            DCoordinate3 tmp = _data_net_to_interpolate(_data_point_element_u_index,_data_point_element_v_index);

            ((MainWindow*)_parent)->_settings_widget->data_point_element_x_spin_box->setValue(tmp.x());
            ((MainWindow*)_parent)->_settings_widget->data_point_element_y_spin_box->setValue(tmp.y());
            ((MainWindow*)_parent)->_settings_widget->data_point_element_z_spin_box->setValue(tmp.z());
        }
    }

    void GLWidget::set_data_point_element_x(double value) {

        if (((MainWindow*)_parent)->_settings_widget->data_point_element_x_spin_box->hasFocus())
        {

            if (_data_net_to_interpolate(_data_point_element_u_index,_data_point_element_v_index).x() != value)
            {
                _data_net_to_interpolate(_data_point_element_u_index,_data_point_element_v_index).x() = value;

                if (_image_of_data_points)
                {
                    (*_image_of_data_points)(_data_point_element_u_index, _data_point_element_v_index) = _data_net_to_interpolate(_data_point_element_u_index, _data_point_element_v_index);
                    _image_of_data_points->UpdateVertexBufferObjectsOfData();
                }

                initialPopulation();

                updateGL();
            }
        }
    }

    void GLWidget::set_data_point_element_y(double value) {

        if (((MainWindow*)_parent)->_settings_widget->data_point_element_y_spin_box->hasFocus())
        {

            if (_data_net_to_interpolate(_data_point_element_u_index,_data_point_element_v_index).y() != value)
            {
                _data_net_to_interpolate(_data_point_element_u_index,_data_point_element_v_index).y() = value;

                if (_image_of_data_points)
                {
                    (*_image_of_data_points)(_data_point_element_u_index, _data_point_element_v_index) = _data_net_to_interpolate(_data_point_element_u_index, _data_point_element_v_index);
                    _image_of_data_points->UpdateVertexBufferObjectsOfData();
                }

                initialPopulation();

                updateGL();
            }
        }
    }

    void GLWidget::set_data_point_element_z(double value) {

        if (((MainWindow*)_parent)->_settings_widget->data_point_element_z_spin_box->hasFocus())
        {

            if (_data_net_to_interpolate(_data_point_element_u_index,_data_point_element_v_index).z() != value)
            {
                _data_net_to_interpolate(_data_point_element_u_index,_data_point_element_v_index).z() = value;

                if (_image_of_data_points)
                {
                    (*_image_of_data_points)(_data_point_element_u_index, _data_point_element_v_index) = _data_net_to_interpolate(_data_point_element_u_index, _data_point_element_v_index);
                    _image_of_data_points->UpdateVertexBufferObjectsOfData();
                }

                initialPopulation();

                updateGL();
            }
        }
    }

    void GLWidget::reset_initialization() {
        initialDataPoints();

        updateGL();
    }

    void GLWidget::save_to_file() {
        QString filename = QFileDialog::getSaveFileName(this, tr("Save"), QString(), tr("My files (*.myfile);;All Files(*)"));

        if (!filename.isEmpty()) {
            if (! (filename.endsWith(".myfile", Qt::CaseInsensitive))) {
                filename += ".myfile";
            }

            string tmp = filename.toUtf8().constData();

            fstream f(tmp.c_str(), ios_base::out);

            f << (PI / _alpha) << endl;

            f << (PI / _beta) << endl;

            f << _n << endl;

            f << _m << endl;

            f << _individual_number << endl;

            f << _data_net_to_interpolate << endl;

            f.close();

            _message_box.setWindowTitle("Save");
            _message_box.setText("File is saved!");
            _message_box.exec();
        }
    }

    void GLWidget::load_from_file() {
        QString filename = QFileDialog::getOpenFileName(this, tr("Open"), QString(), tr("My files (*.myfile);;All Files(*)"));

        if (!filename.isEmpty()) {

            string tmp = filename.toUtf8().constData();

            fstream f(tmp.c_str(), ios_base::in);

            double tmp_d = 0.0;

            f >> tmp_d;
            _alpha = PI / tmp_d;
            ((MainWindow*)_parent)->_settings_widget->alpha_spin_box->setValue(tmp_d);

            f >> tmp_d;
            _beta = PI / tmp_d;
            ((MainWindow*)_parent)->_settings_widget->beta_spin_box->setValue(tmp_d);

            f >> _n;

            f >> _m;

            f >> _individual_number;

            f >> _data_net_to_interpolate;

            f.close();

            reset_values();

            initialPopulation();

            updateGL();

            _message_box.setWindowTitle("Open");
            _message_box.setText("File is loaded!");
            _message_box.exec();
        }
    }

    void GLWidget::reset_values() {
        ((MainWindow*)_parent)->_settings_widget->data_point_size_u_spin_box->setValue(_n);
        ((MainWindow*)_parent)->_settings_widget->data_point_size_v_spin_box->setValue(_m);
        ((MainWindow*)_parent)->_settings_widget->data_point_element_x_spin_box->setValue(0.0);
        ((MainWindow*)_parent)->_settings_widget->data_point_element_y_spin_box->setValue(0.0);
        ((MainWindow*)_parent)->_settings_widget->data_point_element_z_spin_box->setValue(0.0);
        ((MainWindow*)_parent)->_settings_widget->individual_number_spin_box->setValue(_individual_number);
        ((MainWindow*)_parent)->_settings_widget->individual_number_to_show_spin_box->setValue(1);
    }
}

